package com.stu.aq;

import java.util.ArrayList;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import android.media.Ringtone;
import android.media.RingtoneManager;
import android.net.Uri;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Fragment;
import android.app.FragmentManager;
import android.app.FragmentTransaction;
import android.content.DialogInterface;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.graphics.Color;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemSelectedListener;
import android.widget.EditText;
import android.widget.Toast;

import com.stu.status.*;
import com.stu.usb.*;
import com.stu.config.*;
import com.stu.info.*;

public class AQ extends Activity implements CommandFragment.OnCommandListener, OnItemSelectedListener {

	public static boolean DEBUG = false;
	public final static boolean LOGCAT = false;

	private UsbSerialDriver mDriver;
	private ExecutorService mEx;
	private SerialIOManager mIOManager;
	private boolean viewChanged, configChanged, stopped, uploading;
	private VersionFragment version;
	private NumberEntryPopup numberEntry;
	
	private class FragmentTag {
		public String value;

		public final static String STATUS = "0";
		public final static String MOTOR = "1";
		public final static String TRANSMITTER = "2";
		public final static String SENSOR = "3";
		public final static String ALTITUDE = "4";
		public final static String ATTITUDE = "5";
	}
	private static FragmentTag currentFrag;
	
	public class ConfigInfo {
		public String	label;
		public String	tag;
		public ConfigInfo( String l, String t ) {
			label = l;
			tag = t;
		}
		public String toString() { return label; }
	}
	public static ArrayList<ConfigInfo> configInfo;
	public static int	configIndex;

	public static boolean connected;
	public final static int motorMin = 1000;
	public final static int motorMax = 2000;
	public final static int motorTest = 1050;
	public final static int motorCount = 4;
	
	public final static int receiverMax = 2000;
	public final static int receiverMin = 1000;

	// Transmitter Mode 2 Setup
	public final class Transmitter {
		public final static int COUNT = 6;
		public final static int ROLL = 0;
		public final static int PITCH = 1;
		public final static int YAW = 2;
		public final static int THROTTLE = 3;
		public final static int MODE = 4;
		public final static int AUX1 = 5;
	}

	public final class Sensor {
		public final static int COUNT = 9;
		public final static int GYRO_ROLL = 0;
		public final static int GYRO_PITCH = 1;
		public final static int GYRO_YAW = 2;
		public final static int ACCEL_ROLL = 3;
		public final static int ACCEL_PITCH = 4;
		public final static int ACCEL_Z = 5;
		public final static int MAG_X = 6;
		public final static int MAG_Y = 7;
		public final static int MAG_Z = 8;
	}

	public final class Altitude {
		public final static int COUNT = 2;
		public final static int BAROMETER = 0;
		public final static int RANGE = 1;
	}

	public final class Attitude {
		public final static int COUNT = 3;
		public final static int ROLL = 0;
		public final static int PITCH = 1;
		public final static int YAW = 2;
	}

	public final static int Colors[] = {
		Color.rgb( 0, 65, 228 ),
		Color.rgb( 253, 65, 64 ),
		Color.rgb( 15, 255, 0 ),
		Color.rgb( 72, 189, 255 ),
		Color.rgb( 210, 255, 108 ),
		Color.rgb( 207, 89, 253 ),
		Color.rgb( 255, 177, 29 ),
		Color.rgb( 64, 70, 252 ),
		Color.rgb( 255, 65, 184 )
	};

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		
		if( DEBUG && !LOGCAT )
			Log.open( getApplicationContext() );

if(DEBUG) Log.d("aq", "onCreate");
		connected = false;
		viewChanged = false;
		configChanged = false;
		stopped = false;
		uploading = false;

		configInfo = new ArrayList<ConfigInfo>();
		configInfo.add( new ConfigInfo("Rate Mode", "C0") );
		configInfo.add( new ConfigInfo("Attitude Mode", "C1") );
		configInfo.add( new ConfigInfo("Yaw Configuration","C2") );
		configInfo.add( new ConfigInfo("Altitude Hold", "C3") );
		configInfo.add( new ConfigInfo("GPS Position Hold", "C4") );
		configInfo.add( new ConfigInfo("Miscellaneous", "C5") );
		configInfo.add( new ConfigInfo("Transmitter Smoothing", "C6") );
		configInfo.add( new ConfigInfo("Battery Monitor", "C7") );
		configInfo.add( new ConfigInfo("Camera Stabilization", "C8") );
		configIndex = 0;

		SharedPreferences s = PreferenceManager.getDefaultSharedPreferences(this);
		if( !s.contains("motors") ) {
			Editor e = s.edit();
			e.putInt("motors", 4);
			e.commit();
		}

		setContentView(R.layout.main);

		// initial info display is vehicle status
		FragmentManager fm = getFragmentManager();
		FragmentTransaction ft = fm.beginTransaction();
		ft.add(R.id.main, new VehicleStatusFragment(), FragmentTag.STATUS );
		ft.commit();
		currentFrag = new FragmentTag();
		currentFrag.value = FragmentTag.STATUS;
		
		numberEntry = new NumberEntryPopup( this );
	}

	@Override
	protected void onStart() {
		super.onStart();
		if( DEBUG && !LOGCAT )
			Log.open( getApplicationContext() );
if(DEBUG) Log.d("aq", "onStart");
	}

	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		// Inflate the menu; this adds items to the action bar if it is present.
		getMenuInflater().inflate(R.menu.main, menu);
		return true;
	}

	@Override
	public boolean onPrepareOptionsMenu( Menu menu ) {
		MenuItem m = menu.findItem( R.id.connect );
		if( connected )
			m.setTitle( "Disconnect" );
		else
			m.setTitle( "Connect" );
		menu.findItem( R.id.menuLog ).setChecked( DEBUG );
		return true;
	}

	@Override
	protected void onResume() {
		super.onResume();
if(DEBUG) Log.d("aq", "resume");
		clearStatus();
		invalidateOptionsMenu();	// cause options bar to update
	}


	@Override
	protected void onStop() {
		super.onStop();
if(DEBUG) Log.d("aq", "stop connected="+connected);
		if( connected ) {
			connected = false;
			stopped = true;
			((CommandFragment)getFragmentManager().findFragmentById(R.id.main)).stopUpdate( this, mIOManager );
			// wait for everything top stop by checking mDriver
			//  every 100ms up to 1000ms
			final Timer t = new Timer();
			t.scheduleAtFixedRate( new TimerTask() {
				int count = 0;
				public void run() {
					// check for null in case a pending timer event is on the queue
					//  and closeDriver() has been called
					if( mDriver == null || count == 10 ) {
						t.cancel();
					}
					count++;
				}
			}, 0, 100 );
			if( mDriver != null )
				if(DEBUG) Log.d("aq", "onStop timed out");
		}
		if( DEBUG && !LOGCAT )
			Log.close();
	}

	@Override
	public void OnCommandComplete( int fragmentId, CommandFragment.FragmentState state ) {
if(DEBUG) Log.d("aq", "command complete id=" + String.format( "0x%x", fragmentId ) + " state="+state+" connected="+connected+" viewChanged="+viewChanged+" configChanged="+configChanged+" uploading="+uploading);
		switch( fragmentId ) {
			case R.id.versionView:
				// Version info updated
				if( !connected ) {
					// Play a sound
				    Uri notification = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
				    Ringtone r = RingtoneManager.getRingtone(getApplicationContext(), notification);
				    r.play();
				    connected = true;
				    invalidateOptionsMenu();	// update menu to show connected
				}
				// update the current configuration status and call me when done
				// This was not done on the initial OnItemSelected because it was
				//  called during inflation and before the USB connect
				((CommandFragment)getFragmentManager().findFragmentById(R.id.ConfigView)).update( this, mIOManager );
				break;
			case R.id.ConfigView:
				// done updating the config info so now update the vehicle status
				invalidateOptionsMenu();	// cause options bar to update
				if( connected ) {
					if( state == CommandFragment.FragmentState.WRITING ) {
						// config upload done. get written values just to confirm
						((CommandFragment)getFragmentManager().findFragmentById(R.id.ConfigView)).update( this, mIOManager );
					} else {
						// config data read. resume status updating
						if( uploading ) {
							uploading = false;
							Toast t = Toast.makeText( getApplicationContext(), "Upload Complete", Toast.LENGTH_SHORT );
							t.show();
							// Play a sound
							Uri notification = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
							Ringtone r = RingtoneManager.getRingtone(getApplicationContext(), notification);
							r.play();
						}
						// resume status updating
						stopped = false;
						Fragment f = getFragmentManager().findFragmentById(R.id.main);
						if( currentFrag.value == FragmentTag.STATUS )
							((VehicleStatusFragment)f).setButtons( true );
						((CommandFragment)f).update( this, mIOManager );
					}
				}
				break;
			case R.id.main:
				// done updating the current view but we only care if the connection
				//  is being shut down or view or config is being changed
				if( state == CommandFragment.FragmentState.STOPPING ) {
					if( !connected ) {
						// leave the driver open unless OnStop()
						closeIO( stopped );
						// stopped is set by onStop() which mean UI is gone
						if( !stopped ) {
							clearStatus();
							invalidateOptionsMenu();	// cause options bar to update
						}
					} else if( viewChanged ) {
						// stopUpdate has completed so start update on the new view
						viewChanged = false;
						changeView( currentFrag, true );
					} else if( configChanged ) {
						configChanged = false;
						if( connected )
							((CommandFragment)getFragmentManager().findFragmentById(R.id.ConfigView)).update( this, mIOManager );
					} else if( uploading ) {
						// send the config values and call me when done
						if( connected )
							((CommandFragment)getFragmentManager().findFragmentById(R.id.ConfigView)).write( this, mIOManager );
					}
				}
				break;
		}
	}

	@Override
	public void OnCommandError( final int fragmentId, final AQException e ) {
if(DEBUG) Log.e("aq", "OnCommandError id="+String.format("0x%x",  fragmentId)+ " " +e.getMessage());
		connected = false;
		closeIO( true );
		runOnUiThread( new Runnable() {
			public void run() {
				// Must run these on the UI thread
				clearStatus();
				invalidateOptionsMenu();	// cause options bar to update
				AlertDialog.Builder dlg = new AlertDialog.Builder(AQ.this);
				dlg.setTitle( "Error" );
				dlg.setMessage( e.getMessage() );
				dlg.setNegativeButton( "Cancel", null );
				dlg.create().show();
			}
		});
	}
	
	@Override
	public void OnCommandWarning(int fragmentId, AQException e) {
if(DEBUG) Log.w("aq", "OnCommandWarning id="+String.format("0x%x", fragmentId)+ " " +e.getMessage());
	}

	private void closeIO( final boolean closeDriver ) {
if(DEBUG) Log.d("aq", "closeIO");
		try {
			if( mIOManager != null ) {
				mIOManager.setListener( null );
				mIOManager.stop();
				// wait for the SerialIOManager to stop by checking the state flag
				//  every 100ms up to 1000ms
				final Timer t = new Timer();
				t.scheduleAtFixedRate( new TimerTask() {
					int count = 0;
					public void run() {
						// check for null in case a pending timer event is on the queue
						//  and closeDriver() has been called
						if( mIOManager != null ) {
							if( mIOManager.getState() == SerialIOManager.State.STOPPED || count == 10 ) {
								t.cancel();
								if( closeDriver )
									closeDriver();
								else {
									mEx.shutdownNow();
									mIOManager = null;
								}
							}
							count++;
						}
					}
				}, 0, 100 );
			} else {
				if( closeDriver )
					closeDriver();
			}
		} catch( final Exception e ) {
if(DEBUG) Log.d("aq", "error closeIO " +e.getMessage());
			if( !stopped ) {
				runOnUiThread( new Runnable() {
					public void run() {
						AlertDialog.Builder dlg = new AlertDialog.Builder(AQ.this);
						dlg.setTitle( "Error" );
						dlg.setMessage( e.getMessage() );
						dlg.setNegativeButton( "Cancel", null );
						dlg.create().show();
					}
				});
			}
		}
	}
	
	/*
	 * Called after the SerialIOManager is finished to close the USB port.
	 */
	private void closeDriver() {
if(DEBUG) Log.d("aq", "closeDriver");
		try {
			if( mIOManager != null ) {
				if( mIOManager.getState() != SerialIOManager.State.STOPPED ) {
					// timed out waiting on stop
					throw new AQException("IOManager never entered stop state");
				}
				mEx.shutdownNow();
				mIOManager = null;
			}
			if( mDriver != null ) {
				mDriver.close();
				mDriver = null;
			}
		} catch( final Exception e ) {
if(DEBUG) Log.d("aq", "error closeDriver " +e.getMessage());
			if( !stopped ) {
				runOnUiThread( new Runnable() {
					public void run() {
						AlertDialog.Builder dlg = new AlertDialog.Builder(AQ.this);
						dlg.setTitle( "Error" );
						dlg.setMessage( e.getMessage() );
						dlg.setNegativeButton( "Cancel", null );
						dlg.create().show();
					}
				});
			}
		}
	}

	private void clearStatus() {
		// clear the status
		CommandFragment f = (CommandFragment)getFragmentManager().findFragmentById(R.id.main);
		if( f != null )
			f.clear();
		// clear the current config
		f = (CommandFragment)getFragmentManager().findFragmentById(R.id.ConfigView);
		if( f != null )
			f.clear();
	}
	
	/**
	 * Change the current main view
	 * @param tag Tag for new view
	 * @param startUpdate True to start updating the view
	 */
	private void changeView( FragmentTag tag, boolean startUpdate ) {
		CommandFragment f = null;
		switch( tag.value.charAt(0) ) {
			case '0':
				f = new VehicleStatusFragment();
				break;
			case '1':
				f = new MotorCommandsFragment();
				break;
			case '2':
				f = new TransmitterGraphFragment();
				break;
			case '3':
				f = new SensorGraphFragment();
				break;
			case '4':
				f = new AltitudeGraphFragment();
				break;
			case '5':
				f = new AttitudeGraphFragment();
				break;
		}
		if( f != null ) {
			FragmentManager fm = getFragmentManager();
			FragmentTransaction ft = fm.beginTransaction();
			ft.replace(R.id.main, f, tag.value );
			ft.commit();
			fm.executePendingTransactions();	// change the view immediately
			if( startUpdate ) {
				stopped = false;
				if( tag.value.charAt(0) == '0' ) {
					// Vehicle Status View
					// enable the buttons
					((VehicleStatusFragment)f).setButtons( true );
					// update the config first
					((CommandFragment)fm.findFragmentById(R.id.ConfigView)).update( this, mIOManager );
				} else
					f.update( this, mIOManager );
			}
		}
	}

	private UsbSerialProber.Listener mProbeListener = new UsbSerialProber.Listener() {
		@Override
		public void OnDeviceAvailable(UsbSerialDriver d) {
if(DEBUG) Log.d( "aq", "OnDeviceAvailable" );
			mDriver = d;
			mDriver.open();
		    mDriver.setParameters(115200, 8, UsbSerialDriver.STOPBITS_1, UsbSerialDriver.PARITY_NONE);
			mIOManager = new SerialIOManager( d );
			mEx = Executors.newSingleThreadExecutor();
			mEx.submit(mIOManager);
		    // get the software configuration and call me when it's done
			version = new VersionFragment();
			version.update( mIOManager, AQ.this );
		}

		@Override
		public void OnNoDeviceAvailable() {
			AlertDialog.Builder dlg = new AlertDialog.Builder(AQ.this);
			dlg.setTitle( "Error" );
			dlg.setMessage( "No USB Device Found" );
			dlg.setNegativeButton( "Cancel", null );
			dlg.create().show();
		}
	};

	public static CommandFragment getCurrentConfig() {
		switch( configIndex ) {
			case 0:
				return new ConfigRateFragment();
			case 1:
				return new ConfigAttitudeModeFragment();
			case 2:
				return new ConfigYawFragment();
			case 3:
				return new ConfigAltitudeFragment();
			case 4:
				return new ConfigGPSFragment();
			case 5:
				return new ConfigMiscFragment();
			case 6:
				return new ConfigTransmitterFragment();
			case 7:
				return new ConfigBatteryFragment();
			case 8:
				return new ConfigCameraFragment();
		}
		return null;
	}

	public void showEntryDialog( NumberEntryPopup.NumberEntryListener l, EditText t, boolean integerOnly ) {
		numberEntry.show( findViewById(R.id.main), t, l, integerOnly );
	}

	// called when configuration spinner is used
	@Override
	public void onItemSelected(AdapterView<?> parent, View view, int position,
	  long id) {
		if( configIndex != position ) {
if(AQ.DEBUG) Log.d("aq", "selected=" + String.valueOf( position ));
			configIndex = position;
			FragmentManager fm = getFragmentManager();
			FragmentTransaction ft = fm.beginTransaction();
			ft.replace( R.id.ConfigView, getCurrentConfig(), configInfo.get(configIndex).tag );
			ft.commit();
			fm.executePendingTransactions();	// change the view immediately
			// stop status updating so config can update
			if( connected ) {
				configChanged = true;
				((CommandFragment)getFragmentManager().findFragmentById(R.id.main)).stopUpdate( this, mIOManager );
			}
		}
	}

	// called by configuration spinner
	@Override
	public void onNothingSelected(AdapterView<?> parent) {
	}

	/*
	 * Button Callbacks
	 */
	public void OnUpload( View v ) {
		// stop the status updating and when done call the config update
		if( connected ) {
			uploading = true;
			((CommandFragment)getFragmentManager().findFragmentById(R.id.main)).stopUpdate( this, mIOManager );
		}
	}

	public void OnVersion( View v ) {
		version.show( getFragmentManager(), "version" );
	}

	/*
	 * Menu Command Callbacks
	 */
	public void OnConnect( MenuItem m ) {
if(DEBUG) Log.d( "aq", "OnConnect connect="+connected );
		// mProbeListener is called on successful device open
		// The listener will call the status update when the USB is opened
		if( !connected ) {
			if( mDriver == null )
				UsbSerialProber.findFirstDevice( this, mProbeListener );
			else {
				mIOManager = new SerialIOManager( mDriver );
				mEx = Executors.newSingleThreadExecutor();
				mEx.submit(mIOManager);
			    // get the software configuration and call me when it's done
				version = new VersionFragment();
				version.update( mIOManager, AQ.this );
			}
		} else {
			// Disconnect command
			connected = false;
			((CommandFragment)getFragmentManager().findFragmentById(R.id.main)).stopUpdate( this, mIOManager );
		}
	}
	
	public void OnViewStatus( MenuItem m ) {
		if( currentFrag.value != FragmentTag.STATUS ) {
			currentFrag.value = FragmentTag.STATUS;
			// if status is being updated, changeView will be
			//  called by the callback when stopUpdate completes
			if( connected ) {
				viewChanged = true;
				((CommandFragment)getFragmentManager().findFragmentById(R.id.main)).stopUpdate( this, mIOManager );
			} else
				changeView( currentFrag, false );
		}
	}

	public void OnViewMotor( MenuItem m ) {
		if( currentFrag.value != FragmentTag.MOTOR ) {
			currentFrag.value = FragmentTag.MOTOR;
			// if status is being updated, changeView will be
			//  called by the callback when stopUpdate completes
			if( connected ) {
				viewChanged = true;
				((CommandFragment)getFragmentManager().findFragmentById(R.id.main)).stopUpdate( this, mIOManager );
			} else
				changeView( currentFrag, false );
		}
	}

	public void OnViewAttitude( MenuItem m ) {
		if( currentFrag.value != FragmentTag.ATTITUDE ) {
			currentFrag.value = FragmentTag.ATTITUDE;
			// if status is being updated, changeView will be
			//  called by the callback when stopUpdate completes
			if( connected ) {
				viewChanged = true;
				((CommandFragment)getFragmentManager().findFragmentById(R.id.main)).stopUpdate( this, mIOManager );
			} else
				changeView( currentFrag, false );
		}
	}

	public void OnViewSensor( MenuItem m ) {
		if( currentFrag.value != FragmentTag.SENSOR ) {
			currentFrag.value = FragmentTag.SENSOR;
			// if status is being updated, changeView will be
			//  called by the callback when stopUpdate completes
			if( connected ) {
				viewChanged = true;
				((CommandFragment)getFragmentManager().findFragmentById(R.id.main)).stopUpdate( this, mIOManager );
			} else
				changeView( currentFrag, false );
		}
	}

	public void OnViewTransmitter( MenuItem m ) {
		if( currentFrag.value != FragmentTag.TRANSMITTER ) {
			currentFrag.value = FragmentTag.TRANSMITTER;
			// if status is being updated, changeView will be
			//  called by the callback when stopUpdate completes
			if( connected ) {
				viewChanged = true;
				((CommandFragment)getFragmentManager().findFragmentById(R.id.main)).stopUpdate( this, mIOManager );
			} else
				changeView( currentFrag, false );
		}
	}

	public void OnViewAltitude( MenuItem m ) {
		if( currentFrag.value != FragmentTag.ALTITUDE ) {
			currentFrag.value = FragmentTag.ALTITUDE;
			// if status is being updated, changeView will be
			//  called by the callback when stopUpdate completes
			if( connected ) {
				viewChanged = true;
				((CommandFragment)getFragmentManager().findFragmentById(R.id.main)).stopUpdate( this, mIOManager );
			} else
				changeView( currentFrag, false );
		}
	}
	
	public void OnMenuAbout( MenuItem m ) {
		AlertDialog.Builder b = new AlertDialog.Builder( this );
		b.setPositiveButton( android.R.string.ok, new DialogInterface.OnClickListener() {
			@Override
			public void onClick(DialogInterface dialog, int which) {
				dialog.dismiss();
			}
		});
		try {
			b.setMessage( "Version " + getPackageManager().getPackageInfo(getPackageName(), 0).versionName );
		} catch( Exception e ) {
		}
		b.create().show();
	}
	
	public void OnMenuLog( MenuItem m ) {
		boolean c = !m.isChecked();
		m.setChecked( c );
		DEBUG = c;
		if( !LOGCAT ) {
			if( DEBUG )
				Log.open( getApplicationContext() );
			else
				Log.close();
		}
	}
}
